#pragma once

#include <map>
#include <glibmm/refptr.h>
#include <glibmm/ustring.h>
#include <sigc++/sigc++.h>

#include "Exception.hpp"
#include "GObjHelpers.hpp"

namespace BuildNG
{
  class INamedObject : public Interface
  {
  public:
    virtual const Glib::ustring& get_name() const = 0;

    /// This signal is invoked before the name of the
    /// \ref INamedObject is actually changed. This can
    /// be used to validate if the name change is valid,
    /// and if not, throw an exception.
    sigc::signal<void,Glib::RefPtr<INamedObject>,Glib::ustring>   pre_name_change;

    /// This signal is invoked after the name of the
    /// \ref INamedObject has changed. When this signal
    /// is invoked, the name change is certain.
    /// the first string argument is the old name, the
    /// second one the new name.
    sigc::signal<void,Glib::RefPtr<INamedObject>,
      const Glib::ustring&,const Glib::ustring&>                  post_name_change;
  };

  class NamedObject : public Object, public INamedObject
  {
  public:
    NamedObject(){}
    NamedObject(const Glib::ustring &initial_name)
    {
      name = initial_name;
    }

    // it isn't very useful to create an instance of just a NamedObject,
    // most often it's a derived class, but it's not illegal, so...
    static Glib::RefPtr<NamedObject> create()
    {
      return Glib::RefPtr<NamedObject>(new NamedObject());
    }

    static Glib::RefPtr<NamedObject> create(const Glib::ustring &initial_name)
    {
      return Glib::RefPtr<NamedObject>(new NamedObject(initial_name));
    }

    virtual const Glib::ustring& get_name() const { return name; }

    void set_name(const Glib::ustring &name)
    {
      pre_name_change(this_ptr<INamedObject>(),name);
      Glib::ustring old_name = this->name;
      this->name = name;
      post_name_change(this_ptr<INamedObject>(),old_name,name);
    }

  private:
    Glib::ustring name;
  };

  class NameCollisionException : public Exception
  {
  public:
    NameCollisionException(const Glib::ustring &msg)
      : Exception(msg)
    {
    }
  };

  class InvalidNameException : public Exception
  {
  public:
    InvalidNameException(const Glib::ustring &msg)
      : Exception(msg)
    {
    }
  };

  class NamedObjectCollectionBase
  {
  public:
    enum Flags
    {
      Flag_ErrorOnSurroundingWhitespace     = 0,
      Flag_IgnoreSurroundingWhitespace      = 1,
      Flag_SurroundingWhitespaceSignificant = 2,

      Flag_AllowEmptyName                   = 0 << 2,
      Flag_AllowOnlyWhitespaceName          = 1 << 2,

      Flag_CaseSensitive                    = 0 << 3,
      Flag_CaseInsensitive                  = 1 << 3,
    };

    NamedObjectCollectionBase(int flags = 0);
    NamedObjectCollectionBase(const Glib::ustring collection_name,
      const Glib::ustring &item_type_name,int flags = 0);
    ~NamedObjectCollectionBase();

    void add(Glib::RefPtr<INamedObject> obj);

    int get_size() const;

    Glib::RefPtr<INamedObject> try_get(const Glib::ustring &name);

    void set_collection_name(const Glib::ustring &name);
    void set_item_type_name(const Glib::ustring &name);

  private:
    Glib::ustring                   collection_name;
    Glib::ustring                   item_type_name;
    int                             flags;

    struct Item
    {
      Glib::RefPtr<INamedObject>  obj;
      sigc::connection            pre_name_change_connection;
      sigc::connection            post_name_change_connection;
    };

    typedef std::map<Glib::ustring,Item>      ByNameMap;
    typedef ByNameMap::iterator               ByNameMapIt;
    typedef ByNameMap::const_iterator         ByNameMapConstIt;

    ByNameMap                       by_name;

    void pre_name_change_callback(Glib::RefPtr<INamedObject> obj,
      const Glib::ustring &new_name);
    void post_name_change_callback(Glib::RefPtr<INamedObject> obj,
      const Glib::ustring &old_name,const Glib::ustring &new_name);

  public:
    class IteratorBase
    {
    public:
      IteratorBase(NamedObjectCollectionBase *collection);

      bool move_next();
      void reset();
      Glib::RefPtr<INamedObject> cur();

    private:
      bool          initial;
      ByNameMapIt   it;

      NamedObjectCollectionBase* collection;
    };
  };

  // T must implement INamedObject
  template <class T>
  class NamedObjectCollection : public NamedObjectCollectionBase
  {
  public:
    void add(Glib::RefPtr<T> obj)
    {
      NamedObjectCollectionBase::add(obj);
    }

    Glib::RefPtr<T> try_get(const Glib::ustring &name)
    {
      Glib::RefPtr<INamedObject> as_named_obj = NamedObjectCollectionBase::try_get(name);
      if(as_named_obj)
      {
        Glib::RefPtr<T> ret = Glib::RefPtr<T>::cast_dynamic(as_named_obj);
        assert(ret);
        return ret;
      }
      else
        return Glib::RefPtr<T>();
    }

    class Iterator : public IteratorBase
    {
    public:
      Iterator(NamedObjectCollection<T> *collection)
        : IteratorBase(collection)
      {
      }

      Glib::RefPtr<T> cur()
      {
        Glib::RefPtr<INamedObject> as_named_obj = IteratorBase::cur();
        Glib::RefPtr<T> ret = Glib::RefPtr<T>::cast_dynamic(as_named_obj);
        assert(ret);
        return ret;
      }
    };

    Iterator get_iterator()
    {
      return Iterator(this);
    }
  };
}
